//
//  KZBaseModel.swift
//  KZSwiftMoudel
//
//  Created by 朱凯 on 2018/5/10.
//  Copyright © 2018年 JooKey. All rights reserved.
//

import Foundation

enum KZModelMapError: String, Error {
    case stringToDataConverError    = "字符串无法解析为Data"
    case dataToModelDecodeError     = "JSONDecoder解码失败"
}

protocol KZModelConvertible {
    
    // 通过一个json字符串直接转为model
    static func model(from JSONString: String) throws -> Self
    static func model(from dic: [String: Any]) throws -> Self
    // 把model转为字典
    func toDictionary() -> [String: Any]
//    // 把model转为json字符串
//    func toJSONString() -> String?
}

// MARK: ------ 这里直接用一个Class KZBaseModel实现所需的2个协议发现Codable不作用，所以这里用了实现一个KZBaseModel协议的方式实现基类Model
/* MARK: 其他注意
 * JSON属性名和Model变量名不一致则在model添加一个内嵌enum：rawValue为String且符合CodingKeys协议
 * 无法对null数据解析，如果json中包含null的键值对，decode会失败；如果想要对包含null的json也可以进行解析，则需要在model里实现Decodable的方法required init(from: Decoder)方法，如下
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        age = try container.decodeNil(forKey: .age) ? 10 : try container.decode(Int.self, forKey: .age)
        name = try container.decode(String.self, forKey: .name)
    }
 *
 */
/*
 属性映射：如果返回数据键名和属性名不一致，则可以通过以下方式完成映射，注意：一旦定义这个枚举，就需要把所有属性都写一遍，一致的不写value即可
 enum CodingKeys: String, CodingKey {
    case name
    case age
    case list_s = "list"
 }
 */
protocol KZBaseModel: Codable, CustomStringConvertible, KZModelConvertible  {}
extension KZBaseModel {
    
    // MARK: ------ Model Convertible
    static func model(from JSONString: String) throws -> Self {
        
        if let data = JSONString.data(using: .utf8) {
            if let model = try? JSONDecoder().decode(Self.self, from: data) {
                return model
            } else {
                throw KZModelMapError.dataToModelDecodeError
            }
        } else {
            throw KZModelMapError.stringToDataConverError
        }
    }
    static func model(from dic: [String: Any]) throws -> Self {
        
        if let data = try? JSONSerialization.data(withJSONObject: dic, options: []) {
            if let model = try? JSONDecoder().decode(Self.self, from: data) {
                return model
            } else {
                throw KZModelMapError.dataToModelDecodeError
            }
        } else {
            throw KZModelMapError.stringToDataConverError
        }
    }
    func toDictionary() -> [String: Any] {
        let m = Mirror(reflecting: self)
        var dic: [String: Any] = [String: Any]()
        for child in m.children {
            if let key = child.label {
                dic[key] = child.value
            }
        }
        return dic
    }
   
    // MARK: ------ Custom String Convertible
    var description: String {
        
        var desc = ""
        desc += "KZModelDescription...↙\n"
        desc += "~KZType: \(type(of: self))"
        desc += "\n"
        desc += "~KZProperties:\n"
        desc += "{\n"
        let m = Mirror(reflecting: self)
        for child in m.children {
            if let key = child.label {
                let value = child.value
                desc += "   "
                desc += key
                desc += ": "
                desc += "\(value)"
                desc += "\n"
            }
        }
        desc += "}"
        return desc
    }
}


